package com.elitesland.b2c.com.rpc;

import com.elitesland.b2c.com.rpc.file.param.*;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.model.SysConfigProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.io.OutputStream;
import java.util.List;

/**
 * 远程文件调用服务.
 * <p>
 * 调用coordinator的文件服务进行文件的相关操作。
 * swagger地址：https://eltest.elitescloud.com:10443/coordinator/el-fsm-service/doc.html
 * 郑州内网地址：http://192.168.1.50:8084/coordinator/el-fsm-service/doc.html
 *
 * @author Kaiser（wang shao）
 * @date 2021-02-09
 */
@Component
public class RpcFileService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 文件上传
     *
     * @param resource 上传资源
     * @return 上传结果
     * @throws Exception 上传异常
     */
    public ApiResult<StartFileUploadVO> upload(Resource resource) throws Exception {
        // 第一步，先申请上传信息，获得文件号和上传令牌
        ApiResult<CreatFileUploadInfoVO> authResult = authForUpload();
        if (!authResult.isSuccess()) {
            logger.error("文件上传失败:{}", authResult.getMsg());
            throw new BusinessException("上传文件失败");
        }

        // 第二步，持文件号与上传令牌进行上传文件
        return upload(authResult.getData(), resource);
    }

    /**
     * 文件删除
     *
     * @param fileCode 文件编码
     * @return 删除结果
     * @throws Exception 异常
     */
    public ApiResult<String> delete(String fileCode) throws Exception {
        String url = "/deleteFile/{fileCode}";

        ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.PUT, null, String.class, fileCode);

        return convertResult(result.getBody(), new TypeReference<>() {
        });
    }

    /**
     * 删除文件
     *
     * @param fileCode 文件编码
     * @return 删除结果
     * @throws Exception 异常
     */
    public ApiResult<String> delete(List<String> fileCode) throws Exception {
        String url = "/deleteBatch";

        ResponseEntity<String> result = restTemplate.postForEntity(url, fileCode, String.class);

        return convertResult(result.getBody(), new TypeReference<>() {
        });
    }

    /**
     * 文件下载
     * <p>
     * 适用于小文件下载
     *
     * @param fileCode     文件编码
     * @param outputStream 输入流
     * @return 下载结果
     * @throws Exception 调用异常
     */
    public ApiResult<String> download(String fileCode, OutputStream outputStream) throws Exception {
        String url = "/download/{fileCode}";

        try {
            ResponseEntity<byte[]> entity = restTemplate.exchange(url, HttpMethod.GET, null, byte[].class, fileCode);
            IOUtils.write(entity.getBody(), outputStream);
        } catch (Exception e) {
            logger.error("下载文件失败:", e);
            return ApiResult.fail("下载文件失败");
        }
        return ApiResult.ok();
    }

    /**
     * 文件下载
     * <p>
     * 适用于大文件下载
     *
     * @param fileCode     文件编码
     * @param outputStream 输入流
     * @return 下载结果
     * @throws Exception 调用异常
     */
    public ApiResult<String> downloadStreaming(String fileCode, OutputStream outputStream) throws Exception {
        String url = "/download/{fileCode}";

        RequestCallback requestCallback = request -> request.getHeaders().setAccept(List.of(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
        try {
            restTemplate.execute(url, HttpMethod.GET, requestCallback, clientHttpResponse -> {
                IOUtils.copy(clientHttpResponse.getBody(), outputStream);
                return null;
            }, fileCode);
        } catch (Exception e) {
            logger.error("下载文件失败:", e);
            return ApiResult.fail("下载文件失败");
        }
        return ApiResult.ok();
    }

    @PostConstruct
    private void init() {
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.registerModule(new JavaTimeModule());

        restTemplate = new RestTemplateBuilder()
                .requestFactory(this::getClientHttpRequestFactory)
                .rootUri(configProperties.getUpload().getServerUrl())
                .build();
    }

    private ClientHttpRequestFactory getClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(configProperties.getUpload().getServerConnectTimeout());
        requestFactory.setReadTimeout(configProperties.getUpload().getServerConnectTimeout());
        return requestFactory;
    }

    private <T> T convertResult(String rpcResult, TypeReference<T> typeReference) {
        try {
            return objectMapper.readValue(rpcResult, typeReference);
        } catch (JsonProcessingException e) {
            logger.error("文件服务返回结果转换失败：", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 文件上传的第一步，申请授权
     *
     * @return 权限信息
     * @throws Exception 异常
     */
    private ApiResult<CreatFileUploadInfoVO> authForUpload() throws Exception {
        FileUploadInfoVO uploadInfoVO = new FileUploadInfoVO();
        uploadInfoVO.setTenant("EL");
        uploadInfoVO.setProject(configProperties.getUpload().getProjectFlag());
        uploadInfoVO.setBusiness("common");
        uploadInfoVO.setAttribute(FsmUploadAttributeEnum.ACTIVITY.name());
        uploadInfoVO.setType(FsmUploadTypeEnum.FILE.name());
        uploadInfoVO.setUploaderCode("demo");

        String url = "/applyFileUploadInfo";
        ResponseEntity<String> result = restTemplate.postForEntity(url, uploadInfoVO, String.class);
        logger.info("上传文件前授权：{}", result);

        return objectMapper.readValue(result.getBody(), new TypeReference<>() {
        });
    }

    /**
     * 文件上传的第二步，上传文件
     *
     * @param authInfo 权限信息
     * @param resource 文件
     * @return 上传结果
     * @throws Exception 上传异常
     */
    private ApiResult<StartFileUploadVO> upload(CreatFileUploadInfoVO authInfo, Resource resource) throws Exception {
        MultiValueMap<String, Object> requestEntity = new LinkedMultiValueMap<>();
        requestEntity.add("fileCode", authInfo.getFileCode());
        requestEntity.add("fileToKen", authInfo.getFileToKen());
        requestEntity.add("file", resource);


        String url = "/upload";
        ResponseEntity<String> result = restTemplate.postForEntity(url, requestEntity, String.class);
        logger.info("上传结果：{}", result);

        return objectMapper.readValue(result.getBody(), new TypeReference<>() {
        });
    }

    @Autowired
    private SysConfigProperties configProperties;
    private ObjectMapper objectMapper;
    private RestTemplate restTemplate;
}
